﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using TomanuExtensions;
using System.Diagnostics;

namespace RaytracerLib.MathLib.PolynomialFunction
{
    public abstract class Element 
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private Operator m_parent;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        protected List<Element> m_all_children;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        protected List<Element> m_children = new List<Element>(8);

        public Operator Parent
        {
            get
            {
                return m_parent;
            }
            set
            {
                if (value != null)
                    Debug.Assert(value.Children.Contains(this));

                m_parent = value;

                if (Parent != null)
                    Parent.ClearAllChildren();
            }
        }

        private void ClearAllChildren()
        {
            m_all_children = null;

            if (Parent != null)
                Parent.ClearAllChildren();
        }

        public virtual List<Element> Children
        {
            get
            {
                return m_children;
            }
        }

        public IEnumerable<Element> AllChildren
        {
            get
            {
                if (m_all_children == null)
                {
                    m_all_children = new List<Element>();

                    foreach (var child in Children)
                    {
                        m_all_children.Add(child);
                        m_all_children.AddRange(child.AllChildren);
                    }
                }

                return m_all_children;
            }
        }

        public IEnumerable<Element> AllElements
        {
            get
            {
                yield return this;

                foreach (var child in AllChildren)
                    yield return child;
            }
        }

        public IEnumerable<Operator> Parents
        {
            get
            {
                if (Parent == null)
                    yield break;

                yield return Parent;

                foreach (var p in Parent.Parents)
                    yield return p;
            }
        }

        public void Check(bool a_root = true)
        {
            if (a_root)
                Debug.Assert(Parent == null);

            if (Parent != null)
                Debug.Assert(Parent.Children.Contains(this));

            Debug.Assert(Children.All(child => child.Parent == this));

            if (m_all_children != null)
            {
                var temp = m_all_children;
                m_all_children = null;
                AllChildren.Any();
                Debug.Assert(temp.Exact(temp));
            }

            foreach (var child in Children)
                child.Check(false);
        }

        public abstract Element CloneTree();
    }

    public abstract class SymbolElement : Element
    {
    }

    public class VariableSymbol : SymbolElement
    {
        public const string X = "x";
        public const string Y = "y";
        public const string Z = "z";
        public const string DIST = "t";
        public const string DIR = "d";
        public const string START = "s";

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private string m_name;

        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private double m_exponent;

        public VariableSymbol(string a_name, double a_exponent = 1)
        {
            Name = a_name;
            Exponent = a_exponent;
        }

        public string Name
        {
            get
            {
                return m_name;
            }
            set
            {
                m_name = value;

                Debug.Assert(Name != "");
            }
        }

        public double Exponent
        {
            get
            {
                return m_exponent;
            }
            set
            {
                m_exponent = value;

                Debug.Assert(Exponent.IsNumber());
            }
        }

        public override string ToString()
        {
            if (Exponent == 1)
                return Name;
            else
                return String.Format("{0}^{1}", Name, Exponent.ToString(CultureInfo.InvariantCulture));
        }

        public override Element CloneTree()
        {
            return new VariableSymbol(Name, Exponent);
        }
    }

    public class NumberSymbol : SymbolElement
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private double m_value;

        public NumberSymbol(double a_value)
        {
            Value = a_value;
        }

        public double Value
        {
            get
            {
                return m_value;
            }
            set
            {
                m_value = value;

                Debug.Assert(Value.IsNumber());
            }
        }

        public override string ToString()
        {
            return Value.ToString(CultureInfo.InvariantCulture);
        }

        public override Element CloneTree()
        {
            return new NumberSymbol(Value);
        }
    }

    public abstract class Operator : Element
    {
        public T GroupGrandparent<T>() where T : Operator
        {
            Debug.Assert(this is T);

            T act = this as T;

            for (; ; )
            {
                if (act.Parent == null)
                    return act;

                T parent = act.Parent as T;

                if (parent == null)
                    return act;

                act = parent;
            }
        }
    }

    public abstract class BinaryOperator : Operator
    {
        public Element Left
        {
            get
            {
                return m_children[0];
            }
            set
            {
                m_children[0] = value;

                if (value != null)
                    value.Parent = this;
            }
        }

        public Element Right
        {
            get
            {
                return m_children[1];
            }
            set
            {
                m_children[1] = value;

                if (value != null)
                    value.Parent = this;
            }
        }

        public abstract int Priority { get; }

        public BinaryOperator()
            : this(null, null)
        {
        }

        public BinaryOperator(Element a_left, Element a_right)
        {
            m_children.Add(a_left);
            m_children.Add(a_right);

            if (a_left != null)
                a_left.Parent = this;

            if (a_right != null)
                a_right.Parent = this;
        }
    }

    public class AddOperator : BinaryOperator
    {
        public AddOperator()
        {
        }

        public AddOperator(Element a_left, Element a_right)
            : base(a_left, a_right)
        {
        }

        public override string ToString()
        {
            if ((Left == null) && (Right == null))
                return "+";
            else
                return string.Format("{0}+{1}", Left, Right);
        }

        public override int Priority
        {
            get
            {
                return 1;
            }
        }

        public override Element CloneTree()
        {
            return new AddOperator(Left.CloneTree(), Right.CloneTree());
        }
    }

    public class SubOperator : BinaryOperator
    {
        public SubOperator()
        {
        }

        public SubOperator(Element a_left, Element a_right)
            : base(a_left, a_right)
        {
        }

        public override string ToString()
        {
            if ((Left == null) && (Right == null))
                return "-";
            else
            {
                if (Right.AllChildren.Any(el => ((el is AddOperator) || (el is SubOperator))))
                    return string.Format("{0}-({1})", Left, Right);
                else
                    return string.Format("{0}-{1}", Left, Right);
            }
        }

        public override int Priority
        {
            get
            {
                return 1;
            }
        }

        public override Element CloneTree()
        {
            return new SubOperator(Left.CloneTree(), Right.CloneTree());
        }
    }

    public class MulOperator : BinaryOperator
    {
        public MulOperator()
        {
        }

        public MulOperator(Element a_left, Element a_right)
            : base(a_left, a_right)
        {
        }

        public override string ToString()
        {
            if ((Left == null) && (Right == null))
                return "*";
            else
            {
                Element left = TreeTransforms.UnpackVariables(Left.CloneTree());
                Element right = TreeTransforms.UnpackVariables(Right.CloneTree());

                string left_str = Left.ToString();
                if (!(left is MulOperator))
                {
                    if (left.AllElements.Any(el => (el is AddOperator) || (el is SubOperator) ||
                        (el is MinusOperator)))
                    {
                        left_str = "(" + left_str + ")";
                    }
                }

                string right_str = Right.ToString();
                if (!(right is MulOperator))
                {
                    if (right.AllElements.Any(el => (el is AddOperator) || (el is SubOperator) ||
                        (el is MinusOperator)))
                    {
                        right_str = "(" + right_str + ")";
                    }
                }

                return string.Format("{0}*{1}", left_str, right_str);
            }
        }

        public override int Priority
        {
            get
            {
                return 2;
            }
        }

        public override Element CloneTree()
        {
            return new MulOperator(Left.CloneTree(), Right.CloneTree());
        }
    }

    public class DivOperator : BinaryOperator
    {
        public DivOperator()
        {
        }

        public DivOperator(Element a_left, Element a_right)
            : base(a_left, a_right)
        {
        }

        public override string ToString()
        {
            if ((Left == null) && (Right == null))
                return "/";
            else
                return string.Format("({0})/({1})", Left, Right);
        }

        public override int Priority
        {
            get
            {
                return 2;
            }
        }

        public override Element CloneTree()
        {
            return new DivOperator(Left.CloneTree(), Right.CloneTree());
        }
    }

    public class PowOperator : BinaryOperator
    {
        public PowOperator()
        {
        }

        public PowOperator(Element a_left, Element a_right)
            : base(a_left, a_right)
        {
        }

        public PowOperator(Element a_left, double a_right)
            : base(a_left, new NumberSymbol(a_right))
        {
        }

        public override string ToString()
        {
            if ((Left == null) && (Right == null))
                return "^";
            else
            {
                Element left = TreeTransforms.UnpackVariables(Left.CloneTree());
                Element right = TreeTransforms.UnpackVariables(Right.CloneTree());

                string left_str = Left.ToString();
                if (left.AllElements.Any(el => el is Operator))
                    left_str = "(" + left_str + ")";

                string right_str = Right.ToString();
                if (right.AllElements.Any(el => el is Operator))
                    right_str = "(" + right_str + ")";

                return string.Format("{0}^{1}", left_str, right_str);
            }
        }

        public override int Priority
        {
            get
            {
                return 3;
            }
        }

        public override Element CloneTree()
        {
            return new PowOperator(Left.CloneTree(), Right.CloneTree());
        }
    }

    public abstract class UnaryOperator : Operator
    {
        public Element Child
        {
            get
            {
                return m_children[0];
            }
            set
            {
                m_children[0] = value;

                if (value != null)
                    value.Parent = this;
            }
        }

        public UnaryOperator()
            : this(null)
        { 
        }

        public UnaryOperator(Element a_child)
        {
            m_children.Add(a_child);

            if (a_child != null)
                a_child.Parent = this;
        }
    }

    public class MinusOperator : UnaryOperator
    {
        public MinusOperator()
        {
        }

        public MinusOperator(Element a_child)
            : base(a_child)
        {
        }

        public override string ToString()
        {
            if (Child == null)
                return "-";
            else
                return string.Format("-{0}", Child);
        }

        public override Element CloneTree()
        {
            return new MinusOperator(Child.CloneTree());
        }
    }

    public class PlusOperator : UnaryOperator
    {
        public PlusOperator()
        {
        }

        public PlusOperator(Element a_child)
            : base(a_child)
        {
        }

        public override string ToString()
        {
            if (Child == null)
                return "+";
            else
                return Child.ToString();
        }

        public override Element CloneTree()
        {
            return new PlusOperator(Child.CloneTree());
        }
    }

    public class DifferentiateOperator : UnaryOperator
    {
        public readonly string Name;

        public DifferentiateOperator(string a_name)
        {
            Name = a_name;
        }

        public DifferentiateOperator(string a_name, Element a_child)
            : base(a_child)
        {
            Name = a_name;
        }

        public override string ToString()
        {
            if (Child == null)
                return "'";
            else
                return string.Format("({0})'", Child);
        }

        public override Element CloneTree()
        {
            return new DifferentiateOperator(Name, Child.CloneTree());
        }
    }

    public class CloseBracketOperator : Operator
    {
        public override string ToString()
        {
            return ")";
        }

        public override Element CloneTree()
        {
            return new CloseBracketOperator();
        }
    }

    public class OpenBracketOperator : Operator
    {
        public override string ToString()
        {
            return "(";
        }

        public override Element CloneTree()
        {
            return new OpenBracketOperator();
        }
    }
}
